/*********************************************************
*
*  Project:      Utilities
*
*  Class:        ChineseFontChooser
*
*  File name:    ChineseFontChooser.java
*
*
*    SVN:
*
*    $Rev$:
*    $Date$:
*    $LastChangedBy$:
*
*    $HeadURL$:
*
**********************************************************/

package com.eastmountain.chinese.fonts;

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Hashtable;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.BevelBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.eastmountain.util.Helpers;

/*********************************************************
 *
 *  Class:        ChineseFontChooser
 *
 **********************************************************/

public class ChineseFontChooser {

    /**
     * The ChineseFontChooser dialog allows selection of a Font from a list
     * of available fonts that support Chinese characters.
     */
    
    static final String REGULAR = "Regular";
    static final String ITALIC = "Italic";
    static final String BOLD = "Bold";
    static final String FONT_CHOOSER = "Font Chooser";
    static final String FONT_NAME = "Name: ";
    static final String FONT_STYLE = "Style";
    static final String FONT_SIZE = "Size: ";
    static final String ERROR_INVALID_FONT_SIZE = "Error: invalid font size : should be a number";
    static final String OK = "OK";
    static final String CANCEL = "Cancel";
    static final String LOADING_FONTS_PLEASE_WAIT = "Loading fonts, please wait";

    /** Font Name List */
    static String fontNames[];

    /** Name to Font map */
    static Hashtable fontMap;

    /** Possible styles */
    // static String fontStyles[] = { REGULAR, ITALIC, BOLD, BOLD_ITALIC };
    static JCheckBox bold;
    static JCheckBox italic;

    /** Possible sizes */
    static String fontSizes[] = { "8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24",
                                    "26", "28", "32", "36", "48", "72", "240" };

    /** The dialog itself */
    static JDialog dialog;

    /** Font list */
    static JList fontList;

    /** Font size */
    static JComboBox fontSize;

    /** Font preview */
    static FontPreview preview;

    /** Value returned from showDialog */
    static Font returnFont;

    /** Default font */
    //static final Font defaultFont = new Font( "Dialog", Font.BOLD, 48 );
    
    //  TODO set back to dialog font
    
    static final Font defaultFont =new Font( "Arial Unicode MS", Font.BOLD, 48 );
    
    static private Font[] allFonts;

    static final char sampleChineseCharacter = '\u4F60';  // ni3 as in ni3 hao3

    public static void loadFonts() {

        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();

        allFonts = env.getAllFonts();
        int nFonts = allFonts != null ? allFonts.length : 0;
        fontNames = new String[nFonts];
        fontMap = new Hashtable();
        String currentFamily = "";
        int j = 0;

        for ( int i = 0; i < nFonts; i++ ) {

            Font font = allFonts[ i ];

            System.out.println( allFonts[ i ] );

            if ( font.canDisplay( sampleChineseCharacter )) {

                    currentFamily = font.getFamily();

                    Object key = fontMap.put( currentFamily, font );

                    if ( key == null ) {

                        // The currentFamily hasn't been seen yet.

                        fontNames[ j ] = currentFamily;
                        j++;

                    }

            }

        }

        String tmp[] = fontNames;
        fontNames = new String[j];
        System.arraycopy( tmp, 0, fontNames, 0, j );

    }

    /**
     * This may take some time, as loading the fonts is time consuming.
     * Therefore, a 'wait' dialog is shown while the dialog is building.
     */
    static void buildDialog() {

        GridBagConstraints constraints = new GridBagConstraints();

        loadFonts();

        dialog = new JDialog( (Frame) null, FONT_CHOOSER, true );

        fontList = new JList( fontNames );
        bold = new JCheckBox( BOLD );
        italic = new JCheckBox( ITALIC );
        fontSize = new JComboBox( fontSizes );
        //fontSize.setEditable( false );
        fontSize.setRenderer( new FontSizeCellRenderer() );
        preview = new FontPreview( defaultFont, false );

        //
        // Style pad
        //

        JPanel stylePad = new JPanel( new GridBagLayout() );

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.insets = new Insets(0,0,0,0);;

        stylePad.add( bold, constraints );

        constraints.gridx = 0;
        constraints.gridy = 1;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;

        stylePad.add( italic, constraints );

        // Limit sizes

        fontList.setVisibleRowCount( 5 );

        JPanel content = new JPanel( new GridBagLayout() );
        JPanel previewPanel = new JPanel( new GridBagLayout() );

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1;
        constraints.weighty = 1;

        previewPanel.add( preview, constraints );

        JPanel namePanel = new JPanel( new GridBagLayout() );

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;

        namePanel.add( new JLabel( FONT_NAME ), constraints );

        constraints.gridx = 0;
        constraints.gridy = 1;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;

        namePanel.add( new JScrollPane( fontList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                                        ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED ),
                                        constraints );

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 2;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;

        content.add( namePanel, constraints );

        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;

        content.add( new JLabel( FONT_SIZE ), constraints );

        constraints.gridx = 2;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.weightx = 0;
        constraints.weighty = 0;

        content.add( fontSize, constraints );

        constraints.gridx = 3;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.WEST;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.weightx = 1;
        constraints.weighty = 0;

        content.add( Box.createHorizontalGlue(), constraints );

        constraints.gridx = 1;
        constraints.gridy = 1;
        constraints.gridwidth = 2;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.NORTHWEST;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.weightx = 0;
        constraints.weighty = 0;

        content.add( stylePad, constraints );

        constraints.gridx = 0;
        constraints.gridy = 3;
        constraints.gridwidth = GridBagConstraints.REMAINDER;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.CENTER;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1;
        constraints.weighty = 1;

        content.add( previewPanel, constraints );

        //TODO Center the buttons

        JPanel buttonPanel = new JPanel( new GridLayout( 1, 0 ) );
        JButton okButton = new JButton( OK );
        JButton cancelButton = new JButton( CANCEL );
        buttonPanel.add( okButton );
        buttonPanel.add( cancelButton );

        JPanel bp = new JPanel( new GridBagLayout() );

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.EAST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 1;
        constraints.weighty = 0;

        bp.add( Box.createHorizontalGlue(), constraints );

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.anchor = GridBagConstraints.EAST;
        constraints.fill = GridBagConstraints.NONE;
        constraints.weightx = 0;
        constraints.weighty = 0;

        bp.add( buttonPanel, constraints );

        //
        // Set borders
        //
        content.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 5,
                                        5, 5, 5 ), BorderFactory.createCompoundBorder(
                                        BorderFactory.createEtchedBorder(),
                                        BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ) ) );
        buttonPanel.setBorder( BorderFactory.createEmptyBorder( 0, 5, 5, 5 ) );
        previewPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory
                                        .createBevelBorder( BevelBorder.LOWERED ), BorderFactory
                                        .createEmptyBorder( 5, 5, 5, 5 ) ) );

        stylePad.setBorder( BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(),
                                        FONT_STYLE, TitledBorder.LEFT, TitledBorder.TOP ) );

        // dialog.setVisible(false);
        // dialog.setModal(true);

        Container pane = dialog.getContentPane();
        pane.removeAll();

        pane.setLayout( new BorderLayout() );
        pane.add( BorderLayout.CENTER, content );
        pane.add( BorderLayout.SOUTH, bp );
        dialog.pack();

        //
        // Set event handlers
        //

        // Font name change

        fontList.addListSelectionListener( new ListSelectionListener() {

            public void valueChanged( ListSelectionEvent evt ) {

                onFontChange();
            }
        } );

        ActionListener checkBoxListener = new ActionListener() {

            public void actionPerformed( ActionEvent evt ) {

                onFontChange();
            }
        };

        bold.addActionListener( checkBoxListener );
        italic.addActionListener( checkBoxListener );

        // Font size change
        fontSize.addActionListener( new ActionListener() {

            public void actionPerformed( ActionEvent evt ) {

                onFontChange();
            }
        } );

        cancelButton.addActionListener( new ActionListener() {

            public void actionPerformed( ActionEvent evt ) {

                returnFont = null;
                dialog.dispose();
            }
        } );

        okButton.addActionListener( new ActionListener() {

            public void actionPerformed( ActionEvent evt ) {

                returnFont = preview.getFont();
                dialog.dispose();
            }
        } );

        dialog.addWindowListener( new WindowAdapter() {

            @Override
            public void windowClosing( WindowEvent evt ) {

                dialog.setVisible( false );
                dialog.dispose();
            }
        } );

        dialog.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
    }

    public static Font showDialog( String dialogTitle, Font defaultFont ) {

        if ( dialog == null ) {
            buildDialog();
        }

        //
        // Set title
        //

        dialog.setTitle( dialogTitle );

        //
        // Set font we start with
        //

        if ( defaultFont == null ) {
            defaultFont = ChineseFontChooser.defaultFont;
        }

        //  TODO get font that is in use
        
        String fontName = bindName( defaultFont.getFamily() );
        fontList.setSelectedValue( fontName, true );
        String fontSizeStr = "" + defaultFont.getSize();
        fontSize.setSelectedItem( fontSizeStr );

        bold.setSelected( defaultFont.isBold() );
        italic.setSelected( defaultFont.isItalic() );

        preview.setFont( defaultFont );

        //
        // Show dialog
        //

        dialog.toFront();
        dialog.setVisible( true );

        return returnFont;

    }

    private static String bindName( String fontName ) {

        if ( fontMap.get( fontName ) == null ) {
            fontName = fontNames[ 0 ];
        }
        return fontName;
    }

    /**
     * Assuming that one of the font settings has change, this member
     * creates a new Font object from the UI settings and repaints
     * the preview component.
     */
    private static void onFontChange() {

        if ( dialog.isVisible() ) {

            try {

                int iFont = fontList.getMinSelectionIndex();
                int size = Integer.parseInt( (String) fontSize.getSelectedItem() );
                Font font = (Font) fontMap.get( fontNames[ iFont ] );

                // WORK AROUND : TO BE REMOVED WHEN FONT BUG FIXED
                // font = newFont(fontNames[iFont], size);

                //font = newFont( fontNames[ iFont ], size + 1 );
                //preview.setFont( font );
                // END WORK AROUND

                font = newFont( fontNames[ iFont ], size );
                preview.setFont( font );
                preview.repaint();

            } catch ( NumberFormatException e ) {

                JOptionPane.showMessageDialog( null, ERROR_INVALID_FONT_SIZE );
                fontSize.requestFocus();

            }
        }
    }

    private static Font newFont(String fontName, int size){
      Font font = (Font)fontMap.get(fontName);
      Map attrs = new Hashtable();
      attrs.put(TextAttribute.FAMILY, font.getFamily());
      attrs.put(TextAttribute.SIZE, new Float(size));

      if(bold.isSelected()) {
        attrs.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
    } else {
        attrs.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
    }

      if(italic.isSelected()) {
        attrs.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
    } else {
        attrs.put(TextAttribute.POSTURE, TextAttribute.POSTURE_REGULAR);
    }

      Font newFont  = new Font(attrs);

      return newFont;
    }

    //
    // Unit testing for FontChooser
    //

    public static void main(String args[]){
      ChineseFontChooser.showDialog("Chinese Font Chooser Unit Testing", null);
    }


}

class FontPreview extends JComponent {

    Font currentFont, displayFont;
    RenderingHints rh = new RenderingHints( RenderingHints.KEY_ANTIALIASING,
                                    RenderingHints.VALUE_ANTIALIAS_ON );

    static final Font defaultFont = new Font( "Dialog", Font.PLAIN, 12 );
    static final String sampleText = "\u4F60\u597D";
    static final int displayFontSize = 20;

    BufferedImage buffer;
    Graphics2D graphics2D;
    boolean fixedSize;

    public FontPreview( Font font, boolean fixedSize ) {

        this.fixedSize = fixedSize;

        //  TODO Calculate size based on largest point size

        setPreferredSize( new Dimension( 200, 125 ) ); // Can be overridden by a client.
        setFont( font );
    }

    @Override
    public void setFont( Font font ) {

        Font oldFont = this.currentFont;

        if ( font == null ) {
            this.currentFont = defaultFont;
        } else {
            this.currentFont = font;
        }

        if ( fixedSize ) {
            displayFont = new Font( this.currentFont.getName(), this.currentFont.getStyle(), displayFontSize );
        } else {
            displayFont = this.currentFont;
        }

        if ( !this.currentFont.equals( oldFont ) ) {
            prepareBuffer();
        }

        String toolTip = this.currentFont.getFontName() + " " + this.currentFont.getSize();

        if ( this.currentFont.isBold() ) {
            toolTip += " Bold";
        }
        if ( this.currentFont.isItalic() ) {
            toolTip += " Italic";
        }

        setToolTipText( toolTip );

    }

    private void prepareBuffer() {

        Dimension size = getSize();
        Dimension pSize = getPreferredSize();
        if ( size.width <= 0 ) {
            size.width = pSize.width;
        }
        if ( size.height <= 0 ) {
            size.height = pSize.height;
        }

        if ( (buffer == null) || (size.width != buffer.getWidth())
                                        || (size.height != buffer.getHeight()) ) {
            buffer = new BufferedImage( size.width, size.height, BufferedImage.TYPE_INT_ARGB );

            graphics2D = buffer.createGraphics();
        }

        graphics2D.setTransform( new AffineTransform() );
        graphics2D.setComposite( AlphaComposite.Clear );
        graphics2D.fillRect( 0, 0, size.width, size.height );
        graphics2D.setComposite( AlphaComposite.SrcOver );

        repaint();

    }

    @Override
    public Font getFont() {

        return displayFont;
    }

    @Override
    public void setSize( Dimension dim ) {

        super.setSize( dim );
        prepareBuffer();
    }

    @Override
    public void paint( Graphics g ) {

        Graphics2D graphics2D = (Graphics2D)g;

        Dimension size = getSize();

        if ( (buffer == null) || (size.width != buffer.getWidth())
                                        || (size.height != buffer.getHeight()) ) {
            prepareBuffer();
        }

        graphics2D.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
                                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON );

        graphics2D.setFont( displayFont );

        Point lu = Helpers.calculateFieldPosition( graphics2D, sampleText,
                                        buffer.getWidth(), buffer.getHeight(), displayFont );

        graphics2D.drawString( sampleText, lu.x, lu.y );

    }
}

class FontSizeCellRenderer extends JLabel implements ListCellRenderer {

    private DefaultListCellRenderer renderer = new DefaultListCellRenderer();

    private String fontSize;

    public FontSizeCellRenderer() {

        setBorder( new EmptyBorder( 5, 5, 5, 5 ) );

        setOpaque( true );

    }

    public Component getListCellRendererComponent(JList list,
                                                  Object value,
                                                  int index,
                                                  boolean isSelected,
                                                  boolean cellHasFocus) {
        //if (isSelected) {
            setBackground(list.getSelectionBackground());
            setForeground(list.getSelectionForeground());
        //} else {
            setBackground( Color.WHITE);
            setForeground( list.getSelectionForeground());
        //}

        fontSize = (String)value;

        setText( fontSize );

        return this;

    }
}

class saveIt {

    private static int nFonts = 0;
    private static String[] fontNames = new String[nFonts];
    private static Hashtable fontMap = new Hashtable();

/*
 * Font loading control
 */
static class LoadMonitor {

    boolean loaded;
    boolean loading;
}

static LoadMonitor loadMonitor = new LoadMonitor();
static private Font[] allFonts;

/**
 * Loads fonts
 */
public static void loadFontsBackground() {

    if ( !loadMonitor.loaded && !loadMonitor.loading ) {
        boolean doLoad = false;
        synchronized (loadMonitor) {
            if ( !loadMonitor.loading ) {
                loadMonitor.loading = true;
                doLoad = true;
            }
        }

        if ( doLoad ) {

        }
            Thread th = new Thread() {

                @Override
                public void run() {

                    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
                    allFonts = env.getAllFonts();
                    int nFonts = allFonts != null ? allFonts.length : 0;
                    fontNames = new String[nFonts];
                    fontMap = new Hashtable();
                    String currentFamily = "";
                    int j = 0;
                    for ( int i = 0; i < nFonts; i++ ) {

                        Font font = allFonts[ i ];

                        // Note that only one font is kept in the hashtable
                        // per family.

                        if ( !currentFamily.equals( font.getFamily() ) ) {

                            currentFamily = font.getFamily();
                            fontNames[ j ] = currentFamily;
                            fontMap.put( currentFamily, font );
                            j++;

                        }

                    }

                    String tmp[] = fontNames;
                    fontNames = new String[j];
                    System.arraycopy( tmp, 0, fontNames, 0, j );

                    synchronized (loadMonitor) {
                        loadMonitor.loaded = true;
                        loadMonitor.loading = false;
                        loadMonitor.notifyAll();
                    }
                };
            };
            th.setPriority( Thread.currentThread().getPriority() - 1 );
            th.start();
        }
    }
}

